#include "DocumentTreeModel.h"

#include "Document.h"

#include "core/core.h"

namespace LDO
{

    class DocumentTreeModelPrivate
    {
        Q_DISABLE_COPY(DocumentTreeModelPrivate)
        Q_DECLARE_PUBLIC(DocumentTreeModel)
        DocumentTreeModel * const q_ptr;

    public:
        explicit DocumentTreeModelPrivate(DocumentTreeModel *model):
            q_ptr(model)
        {

        }

        ~DocumentTreeModelPrivate()
        {
        }

        inline IDocumentObject *object(const QModelIndex &index) const
        {
            return static_cast<IDocumentObject*>(index.internalPointer());
        }

        inline IDocumentObject *objectOrDocument(const QModelIndex &index) const
        {
            if (!index.isValid())
                return document;
            else
                return object(index);
        }

        Document *document = nullptr;
    };
}

using namespace LDO;

DocumentTreeModel::DocumentTreeModel(QObject *parent)
    : QAbstractItemModel(parent)
    , IDocumentObjectListener()
    , d_ptr(new DocumentTreeModelPrivate(this))
{
}

DocumentTreeModel::~DocumentTreeModel()
{

}

void DocumentTreeModel::setDocument(Document *document)
{
    Q_D(DocumentTreeModel);

    if (d->document != nullptr)
        stopListeningRecursive(d->document);

    beginResetModel();
    d->document = document;
    endResetModel();

    if (d->document != nullptr)
        startListeningRecursive(d->document);
}

IDocumentObject *DocumentTreeModel::documentObject(const QModelIndex &index) const
{
    Q_D(const DocumentTreeModel);
    return d->object(index);
}

QModelIndex DocumentTreeModel::index(const IDocumentObject *object) const
{
    Q_D(const DocumentTreeModel);
    QModelIndex modelIndex;
    if (object != d->document)
        modelIndex = createIndex(object->parentObjectIndex(), 0, const_cast<IDocumentObject*>(object));
    return modelIndex;
}

QModelIndex DocumentTreeModel::index(int row, int column, const QModelIndex &parent) const
{
    Q_D(const DocumentTreeModel);

    if (!hasIndex(row, column, parent))
        return QModelIndex();

    if (d->document == nullptr)
        return QModelIndex();

    const IDocumentObject *parentItem = d->objectOrDocument(parent);

    IDocumentObject *childItem = parentItem->childObject(row);
    if (childItem != nullptr)
        return createIndex(row, column, childItem);
    else
        return QModelIndex();
}

QModelIndex DocumentTreeModel::parent(const QModelIndex &index) const
{
    Q_D(const DocumentTreeModel);

    if (!index.isValid())
        return QModelIndex();

    IDocumentObject *childItem = d->object(index);
    IDocumentObject *parentItem = childItem->parentObject();

    if (parentItem == d->document)
        return QModelIndex();

    return createIndex(parentItem->parentObjectIndex(), 0, parentItem);
}

int DocumentTreeModel::rowCount(const QModelIndex &parent) const
{
    Q_D(const DocumentTreeModel);

    if (parent.column() > 0)
        return 0;

    if (d->document == nullptr)
        return 0;

    const IDocumentObject *parentItem = d->objectOrDocument(parent);
    int count = 0;
    for (int i=0; i<parentItem->childObjectCount(); i++)
        if (parentItem->childObject(i)->hasEditor())
            count++;
    return count;
}

int DocumentTreeModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return ColumnCount;
}

QVariant DocumentTreeModel::data(const QModelIndex &index, int role) const
{
    Q_D(const DocumentTreeModel);

    if (!index.isValid())
        return QVariant();

    const IDocumentObject *item = d->object(index);

    switch (role) {
        case Qt::DisplayRole:
        case Qt::EditRole:
            switch (index.column())
            {
                case NameColumn:
                    return item->objectUserName();
                default:
                    return QVariant();
            }
            break;
        case Qt::DecorationRole:
            switch (index.column())
            {
                case NameColumn:
                {
                    const QString name = d->document->iconName(item);
                    return Core::icon(name);
                }
                default:
                    return QVariant();
            }
        default:
            break;
    }

    return QVariant();
}


QVariant DocumentTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation != Qt::Horizontal)
        return QVariant();

    if (role != Qt::DisplayRole)
        return QVariant();

    switch (section)
    {
        case NameColumn:
            return "Name";
        default:
            return QVariant();
    }
}

Qt::ItemFlags DocumentTreeModel::flags(const QModelIndex &index) const
{
    return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}


bool DocumentTreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    Q_D(const DocumentTreeModel);

    if (!index.isValid())
        return false;

    if (role != Qt::EditRole)
        return false;

    if (index.column() != NameColumn)
        return false;

    if (!value.canConvert<QString>())
        return false;

    d->object(index)->setObjectUserName(value.toString());

    return true;
}

void DocumentTreeModel::documentObjectAboutToBeInserted(IDocumentObject *parent,
                                                        IDocumentObject *child,
                                                        int index)
{
    Q_UNUSED(child)

    Q_D(const DocumentTreeModel);

    if (!child->hasEditor())
        return;

    QModelIndex modelIndex;
    if (parent != d->document)
        modelIndex = createIndex(parent->parentObjectIndex(), 0, const_cast<IDocumentObject*>(parent));
    emit beginInsertRows(modelIndex, index, index);
}

void DocumentTreeModel::documentObjectInserted(IDocumentObject *parent,
                                               IDocumentObject *child,
                                               int index)
{
    Q_UNUSED(parent)
    Q_UNUSED(child)
    Q_UNUSED(index)

    if (!child->hasEditor())
        return;

    emit endInsertRows();
    startListeningRecursive(child);
}

void DocumentTreeModel::documentObjectAboutToBeRemoved(IDocumentObject *parent,
                                                       IDocumentObject *child,
                                                       int index)
{
    Q_UNUSED(child)

    Q_D(const DocumentTreeModel);

    if (!child->hasEditor())
        return;

    QModelIndex modelIndex;
    if (parent != d->document)
        modelIndex = createIndex(parent->parentObjectIndex(), 0, const_cast<IDocumentObject*>(parent));
    emit beginRemoveRows(modelIndex, index, index);
}

void DocumentTreeModel::documentObjectRemoved(IDocumentObject *parent,
                                              IDocumentObject *child,
                                              int index)
{
    Q_UNUSED(parent)
    Q_UNUSED(child)
    Q_UNUSED(index)

    if (!child->hasEditor())
        return;

    emit endRemoveRows();
    stopListeningRecursive(child);
}

void DocumentTreeModel::documentObjectAboutToChangeProperty(const IDocumentObject *object,
                                                            const QString &name,
                                                            const QVariant &oldValue)
{
    Q_UNUSED(object)
    Q_UNUSED(name)
    Q_UNUSED(oldValue)
}

void DocumentTreeModel::documentObjectPropertyChanged(const IDocumentObject *object,
                                                      const QString &name, const
                                                      QVariant &newValue)
{
    Q_UNUSED(name)
    Q_UNUSED(newValue)

    Q_D(const DocumentTreeModel);

    QModelIndex modelIndex;
    if (object != d->document)
        modelIndex = createIndex(object->parentObjectIndex(), 0, const_cast<IDocumentObject*>(object));
    emit dataChanged(modelIndex, modelIndex);
}

void DocumentTreeModel::startListeningRecursive(IDocumentObject *object)
{
    if (object == nullptr)
        return;

    beginListeningToDocumentObject(object);
    for (int i = 0; i < object->childObjectCount(); i++)
        if (object->childObject(i)->hasEditor())
            startListeningRecursive(object->childObject(i));
}

void DocumentTreeModel::stopListeningRecursive(IDocumentObject *object)
{
    if (object == nullptr)
        return;

    for (int i = 0; i < object->childObjectCount(); i++)
        if (object->childObject(i)->hasEditor())
            stopListeningRecursive(object->childObject(i));
    endListeningToDocumentObject(object);
}
